#include "parser.h"

/* stores last token that was to be processed */
bool incomplete_parse = false;

enum tokens {
    P_DURATION = 0, P_FILE, P_ALBUM_TAG, P_ALBUMARTIST_TAG, P_ARTIST_TAG,
    P_COMMENT_TAG, P_DATE_TAG, P_GENRE_TAG, P_TITLE_TAG, P_DISCNUMBER_TAG,
    P_TRACKNUMBER_TAG, P_AAA_MODE, P_STATUS, P_VOL_LEFT, P_VOL_RIGHT,
    P_POSITION, P_REPEAT, P_REPEAT_CURRENT, P_SHUFFLE
};

char *token_begin[] = {
    "duration ",
    "file ",
    "tag album ",
    "tag albumartist ",
    "tag artist ",
    "tag comment ",
    "tag date ",
    "tag genre ",
    "tag title ",
    "tag discnumber ",
    "tag tracknumber ",
    "set aaa_mode ",
    "status ",
    "set vol_left ",
    "set vol_right ",
    "position ",
    "set repeat ",
    "set repeat_current ",
    "set shuffle "
};

static char *line_end(char *str) {
    char *p;
    if ((p = strchr(str, '\n')) != NULL)
        return p;
    return (p = strchr(str, '\0'));
}

static char *substr(char *begin, char *end) {
    size_t len = end - begin;
    if (len <= 0)
        return NULL;
    char *substr = malloc(len + 1);
    memset(substr, 0, len + 1);
    strncpy(substr, begin, len);
    assert(substr[len] == '\0');
    return substr;
}

static void reassign_uint64_if_changed(uint64_t *old, char *old_str) {
    /* as per documentation, reset errno for strtoul */
    errno = 0;
    uint64_t new = strtoul(old_str, NULL, 0);
    /* check for conversion errors */
    if (new == 0 && (errno == EINVAL || errno == ERANGE))
        fprintf(stderr, "error converting uint string: %s\n", old_str);
    /* only change what changed */
    if (*old != new)
        *old = new;
}

static void reassign_str_if_changed(char *new, char **old) {
    if (*old == NULL || strcmp(new, *old) != 0) {
        if (DEBUG)
            printf("str changed %s\n", new);
        free(*old);
        *old = strdup(new);
    }
}

void fill_track_or_player(track *track, player *player, char *buf, int buf_len) {
    /* todo: more sophisticated parsing and handling */
    if (incomplete_parse) {
        fprintf(stderr, "incomplete parse detected!\n");
        incomplete_parse = false;
    }

    for (int i = 0; i < P_SHUFFLE + 1; i++) {
        /* search for token sequence */
        char *begin_p = strstr(buf, token_begin[i]);
        if (begin_p == NULL)
            continue;
        /* skip the begin sequences itself */
        begin_p += strlen(token_begin[i]);
        /* search for end, beginning at begin_p */
        char *end_p = line_end(begin_p);

        char *part = substr(begin_p, end_p);
        if (part == NULL)
            continue;

        /* oh boy, here we go */
        switch (i) {
            case P_DURATION: {
                reassign_uint64_if_changed(&track->duration, part);
                break;
            }

            case P_FILE: {
                /* special case, but kind of ugly */
                if (track->file == NULL || strcmp(part, track->file) != 0) {
                    if (DEBUG)
                        printf("str changed %s\n", part);
                    free(track->file);
                    track->file = strdup(part);
                    file_changed = true;
                }
                break;
            }

            case P_ALBUM_TAG: {
                reassign_str_if_changed(part, &track->album_tag);
                break;
            }

            case P_ALBUMARTIST_TAG: {
                reassign_str_if_changed(part, &track->albumartist_tag);
                break;
            }

            case P_ARTIST_TAG: {
                reassign_str_if_changed(part, &track->artist_tag);
                break;
            }

            case P_COMMENT_TAG: {
                reassign_str_if_changed(part, &track->comment_tag);
                break;
            }

            case P_DATE_TAG: {
                reassign_str_if_changed(part, &track->date_tag);
                break;
            }

            case P_GENRE_TAG: {
                reassign_str_if_changed(part, &track->genre_tag);
                break;
            }

            case P_TITLE_TAG: {
                reassign_str_if_changed(part, &track->title_tag);
                break;
            }

            case P_DISCNUMBER_TAG: {
                reassign_uint64_if_changed(&track->discnumber_tag, part);
                break;
            }

            case P_TRACKNUMBER_TAG: {
                reassign_uint64_if_changed(&track->tracknumber_tag, part);
                break;
            }

            case P_AAA_MODE: {
                reassign_str_if_changed(part, &player->aaa_mode);
                break;
            }

            case P_STATUS: {
                reassign_str_if_changed(part, &player->status);
                break;
            }

            case P_VOL_LEFT: {
                reassign_uint64_if_changed(&player->vol_left, part);
                break;
            }

            case P_VOL_RIGHT: {
                reassign_uint64_if_changed(&player->vol_right, part);
                break;
            }

            case P_POSITION: {
                reassign_uint64_if_changed(&player->position, part);
                break;
            }

            case P_REPEAT: {
                player->repeat = strcmp(part, "true") == 0;
                break;
            }

            case P_REPEAT_CURRENT: {
                player->repeat_current = strcmp(part, "true") == 0;
                break;
            }

            case P_SHUFFLE: {
                player->shuffle = strcmp(part, "true") == 0;
                break;
            }

            default:
                fprintf(stderr, "default case during parsing reached!\n");
                break;

            if (*end_p == '\0')
                incomplete_parse = true;
        }

        /* free previously assigned part */
        free(part);
    }
}